import chat from './service';


type Server {
    users:   Reactive[Map[String,User]],
    msg-bus: Bus[chat::Message]
};

type User {
    conn: rpc::Connection
};

type Session impl(chat::Service) {
    id:  String,
    ctx: Server
};

method Session.say: rpc::Method[chat::MessageContent,()]
    &(session) => &(content) =>
        let msg := { |chat::Message| {
            from: session.id,
            content
        } },
        (session.ctx.msg-bus <- msg);

method Session.receive-messages: rpc::Method*[(),chat::Message]
    &(session) => &(_) =>
        { watch session.ctx.msg-bus };

function new-session:
    &(Server,chat::Login,rpc::Connection) => Async[Session,Error]
    &(ctx,login,conn) =>
        let { nickname } := login,
        let id := nickname,
        let user := { |User| { conn } },
        let try-login: Sync[Bool] :=
            let { users } := ctx,
            \ old-users := sync users.{->},
            switch old-users.{ insert (id, user) }:
            case Some new-users:
                \ sync (users <- new-users),
                { yield Yes },
            case None:
                { yield No },
            end,
        \ ok := await try-login,
        if ok:
            { yield { |Session| { ctx, id } } },
        else:
            { throw { Error {
                message: { "nickname '?' is in use" id },
                data: [(chat::ERR_KIND, chat::ERR_NAME_IN_USE)]
            } } };

function delete:
    &(Session) => Sync
    &{id,ctx} =>
        (ctx.users update &(old-users) => (old-users delete* id));

do
    \ users := sync { Reactive { Map::[User] [] } },
    \ msg-bus := sync { Bus::[chat::Message] () },
    let ctx := { |Server| { users, msg-bus } },
    { rpc::serve {
        service: chat::Service,
        backend: chat::DefaultServerBackend,
        options: {},
        constructor: &(arg,conn) =>
            \ session := await { new-session (ctx,arg,conn) },
            { yield { |chat::Service| session } }
                . { with conn
                    .{ on-close }
                    .{ catch &(_) => { yield () } }
                    .{ then { delete session } } }
    } }
    . { crash-on-error };
